package lk.icta.core.service.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.ELandRuntimeException;
import lk.icta.common.ErrorCodes;
import lk.icta.common.entity.Document;
import lk.icta.common.entity.GNDivision;
import lk.icta.common.service.DocumentManagementService;
import lk.icta.common.service.MailSenderService;
import lk.icta.core.dao.LandAssociationDao;
import lk.icta.core.entity.Kachchery;
import lk.icta.core.entity.LandAssociation;
import lk.icta.core.service.AlienationManagementService;
import lk.icta.core.service.KachcheryManagementService;
import lk.icta.core.service.LandAssociationManagementService;
import lk.icta.core.service.LandManagementService;
import lk.icta.user.entity.User;
import lk.icta.core.entity.Land;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * @author Supun Nimesh Karunathilaka
 * @author Supun Viraj Rathnayaka
 */
public class LandAssociationManagementServiceImpl implements LandAssociationManagementService {

    private static final Logger logger = LoggerFactory.getLogger(LandAssociationManagementServiceImpl.class);

    private final LandAssociationDao landAssociationDAO;
    private final DocumentManagementService documentManagementService;
    private final KachcheryManagementService kachcheryManagementService;
    private final MailSenderService mailSenderService;
    private final LandManagementService landManagementService;
    private final AlienationManagementService alienationManagementService;


    public LandAssociationManagementServiceImpl(LandAssociationDao landAssociationDAO,
                                                DocumentManagementService documentManagementService,
                                                KachcheryManagementService kachcheryManagementService,
                                                MailSenderService mailSenderService,
                                                LandManagementService landManagementService,
                                                AlienationManagementService alienationManagementService) {
        this.landAssociationDAO = landAssociationDAO;
        this.documentManagementService = documentManagementService;
        this.kachcheryManagementService = kachcheryManagementService;
        this.mailSenderService = mailSenderService;
        this.landManagementService = landManagementService;
        this.alienationManagementService = alienationManagementService ;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addLandAlienationMetadata(lk.icta.core.entity.LandAssociation landAssociation,long landId, boolean isDefineAM, User user) {
        Land land = landManagementService.getLandById(landId);
        if (isDefineAM) {
            landAssociation.setDefineAMState(lk.icta.core.entity.LandAssociation.DefineAMState.DATA_ENTRY);
            setLandLogComment("Defined alienation method ", land, user);
        } else {
            landAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.DATA_ENTRY);
            setLandLogComment("Added land association ", land, user);
        }
        //Update the land approval note
        landManagementService.updateLandApprovalNote(land, user);
        this.landAssociationDAO.addLandAlienationMetadata(landAssociation, user);
        if (landAssociation.getDocumentList() != null) {
            try {
                logger.debug("Uploading files");
                documentManagementService.uploadfiles(user, landAssociation.getDocumentList(), landAssociation.getId());
            } catch (Exception e) {
                logger.debug("Error :{}", e);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateLandAlienationMetadata(lk.icta.core.entity.LandAssociation landAssociation, long landId, boolean isDefineAM, User user) {
        Land land = landManagementService.getLandById(landId);
        if (isDefineAM) {
            landAssociation.setDefineAMState(lk.icta.core.entity.LandAssociation.DefineAMState.DATA_ENTRY);
            setLandLogComment("Defined alienation method  ", land, user);
        } else {
            landAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.DATA_ENTRY);
            setLandLogComment("Modified land association ", land, user);
        }
        //Update the land approval note
        landManagementService.updateLandApprovalNote(land, user);
        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
        if (landAssociation.getDocumentList() != null) {
            try {
                logger.debug("Uploading files");
                documentManagementService.uploadfiles(user, landAssociation.getDocumentList(), landAssociation.getId());
            } catch (Exception e) {
                logger.debug("Error :{}", e);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateLandAssociationForDefineAM(int alienationMethod, List<Document> docs, LandAssociation la, Land land, List<Long> deleteDocList, User user) {
        LandAssociation landAssociation = getAlienationMethodMetadataByLand(land.getId(), user);
        if(la.getVersion() == landAssociation.getVersion()){
            landAssociation.setLand(land);
            landAssociation.setAlienationMethod(alienationManagementService.getAlienationMethodById(alienationMethod, user));
            //delete documents
            documentManagementService.deleteDocumentAtLandAssociationDefineAM(deleteDocList,user);
            landAssociation.setDocumentList(docs);
            updateLandAlienationMetadata(landAssociation, land.getId(), true, user);
        }else{
            handleELandRuntimeException("The record already modified", ErrorCodes.ILLEGAL_STATE);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void addLandKachcheryMatadata(lk.icta.core.entity.LandAssociation landAssociation, User user) {

        this.landAssociationDAO.addLandAlienationMetadata(landAssociation, user);
        if (landAssociation.getDocumentList() != null) {
            try {
                logger.debug("Uploading files");
                documentManagementService.uploadfiles(user, landAssociation.getDocumentList(), landAssociation.getId());
            } catch (Exception e) {
                logger.debug("Error :{}", e);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateLandKachcheryMatadata(lk.icta.core.entity.LandAssociation landAssociation, User user) {

        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
        if (landAssociation.getDocumentList() != null) {
            try {
                logger.debug("Uploading files");
                documentManagementService.uploadfiles(user, landAssociation.getDocumentList(), landAssociation.getId());
            } catch (Exception e) {
                logger.debug("Error :{}", e);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void approveLandKachcheryMatadata(lk.icta.core.entity.LandAssociation landAssociation, User user) {

        landAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.KACHCHERY_DATA_ENTRY);
        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
        if (landAssociation.getDocumentList() != null) {
            try {
                logger.debug("Uploading files");
                documentManagementService.uploadfiles(user, landAssociation.getDocumentList(), landAssociation.getId());
            } catch (Exception e) {
                logger.debug("Error :{}", e);
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void saveLandKachchery(List<LandAssociation> landAssociationList, Kachchery kachchery, User user) {
        try {
            LandAssociation landAssociation;
            Set<LandAssociation> associationLandSet = new HashSet<LandAssociation>();
            for (LandAssociation association : landAssociationList) {

                landAssociation = getAlienationMethodMetadataByLand(association.getLand().getId(), user);
                if (landAssociation != null) {

                    if (user.getRole().getId().equals(AppConstants.PLC_USER_ROLE)) {
                        association.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.KACHCHERY_APPROVED);
                    } else if (user.getRole().getId().equals(AppConstants.LO_USER_ROLE) && kachchery.getStatus() == Kachchery.Status.INACTIVE) {
                        association.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.REJECTED);
                    } else {
                        association.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.KACHCHERY_DATA_ENTRY);
                    }
                    updateLandKachcheryMatadata(association, user);
                } else {
                    association.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.KACHCHERY_DATA_ENTRY);
                    addLandKachcheryMatadata(association, user);
                }
                associationLandSet.add(association);
            }

            //delete association
            Kachchery oldKachchery = kachcheryManagementService.getKachcheryById(kachchery.getId());
            if (oldKachchery != null) {
                Set<LandAssociation> oldAssociationLandSet = oldKachchery.getLandAssociationList();

                if (oldAssociationLandSet != null) {
                    long associateId;
                    boolean res = false;
                    Iterator<LandAssociation> associationIterator = oldAssociationLandSet.iterator();
                    while (associationIterator.hasNext()) {
                        LandAssociation oldAssociation = associationIterator.next();
                        if (oldAssociation != null) {
                            associateId = oldAssociation.getId();
                            for (LandAssociation as : landAssociationList) {
                                if (associateId == as.getId()) {
                                    res = false;
                                    break;
                                } else {
                                    res = true;
                                }
                            }
                            if (res) {
                                oldAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.REJECTED);
                                updateLandKachcheryMatadata(oldAssociation, user);
                            }
                        }
                    }
                }
            }
            kachchery.setLandAssociationList(associationLandSet);
            if (user.getRole().getId().equals(AppConstants.LO_USER_ROLE)) {

                Kachchery existKachchery = kachcheryManagementService.getKachcheryById(kachchery.getId());
                if (existKachchery != null) {
                    kachcheryManagementService.updateKachchery(kachchery, user);
                } else {
                    kachcheryManagementService.addKachchery(kachchery, user);
                }


            } else if (user.getRole().getId().equals(AppConstants.ORDS_USER_ROLE)) {
                kachcheryManagementService.updateKachchery(kachchery, user);
            } else if (user.getRole().getId().equals(AppConstants.PLC_USER_ROLE)) {
                kachcheryManagementService.updateKachchery(kachchery, user);
            }

            if (kachchery.getDocumentList() != null) {
                try {
                    logger.debug("Uploading files");
                    documentManagementService.uploadfiles(user, kachchery.getDocumentList(), kachchery.getId());
                } catch (Exception e) {
                    logger.debug("Error :{}", e);
                }
            }


        } catch (Exception e) {
            logger.error("Error :{}", e);
        }


    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllAlienationMethodMetadataByLand(long id, User user) {
        return landAssociationDAO.getAllAlienationMethodMetadataByLand(id, user);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public lk.icta.core.entity.LandAssociation getAlienationMethodMetadataById(long id, User user) {
        return landAssociationDAO.getAlienationMethodMetadataById(id, user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void approveLandAlienationMetadata(lk.icta.core.entity.LandAssociation landAssociation, User user) {
        landAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.APPROVED);
        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void approveLandAlienationMetadata(long metadataId, String associationComment, User user, long id) {
        lk.icta.core.entity.LandAssociation landAssociation = getAlienationMethodMetadataById(metadataId, user);
        landAssociation.setAssociationComment(associationComment);
        landAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.APPROVED);
        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
        // Notify to all

        Land l =landManagementService.getLandById(id);
        String emailSubject="Approval of Associated Alienation Methods for land";
        String landDetails="Associated alienation methods for the land referenced by serial number : " + l.getId();
        //Update the land approval note
        setLandLogComment("Approved land association ",l,user);
        landManagementService.updateLandApprovalNote(l,user);



        String emailedMessage= null;
        emailedMessage= landDetails + " has been approved by ";

        GNDivision GNDiv = landManagementService.getLandById(id).getGnDivision();
        mailSenderService.notifyToAll(GNDiv, user,emailSubject, emailedMessage);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void approveLandAlienationMetadataForDefineAM(long metadataId, String defineComment, User user, LandAssociation la) {
        lk.icta.core.entity.LandAssociation landAssociation = getAlienationMethodMetadataById(metadataId, user);
        landAssociation.setDefineComment(defineComment);
        landAssociation.setDefineAMState(lk.icta.core.entity.LandAssociation.DefineAMState.APPROVED);

        if(la.getVersion() == landAssociation.getVersion() ){
             this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
        }else{
             handleELandRuntimeException("The record already modified", ErrorCodes.ILLEGAL_STATE);
        }
        Land l =landAssociation.getLand();
        //Update the land approval note
        setLandLogComment("Approved defined alienation method ",l,user);
        landManagementService.updateLandApprovalNote(l,user);

        String emailSubject="Approval of Defined Alienation Method for land";
        String alienationMethod=landAssociation.getAlienationMethod().getEnName();
        String landDetails="The defined alienation method of " + alienationMethod + " for the land referenced by serial number : " + l.getId();
        String gisParcelCode=null;


        String emailedMessage= null;

        if (gisParcelCode!=null){
            emailedMessage= landDetails + " with GIS Parcel Code : " + gisParcelCode + " has been approved by ";
        }
        else{
           emailedMessage= landDetails + " has been approved by ";
        }
        GNDivision GNDiv = l.getGnDivision();
        mailSenderService.notifyToAll(GNDiv, user,emailSubject, emailedMessage);

    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void rejectLandAlienationMetadata(long metadataId, String associationComment, boolean isDefineAM, User user, long id, LandAssociation la) {
        lk.icta.core.entity.LandAssociation landAssociation = getAlienationMethodMetadataById(metadataId, user);

        if (isDefineAM) {
            // Need to handle versioning only at define AM
            if(la.getVersion() != landAssociation.getVersion() ){
                handleELandRuntimeException("The record already modified", ErrorCodes.ILLEGAL_STATE);
            }
            landAssociation.setDefineComment(associationComment);
            landAssociation.setDefineAMState(lk.icta.core.entity.LandAssociation.DefineAMState.REJECTED);
        } else {
            landAssociation.setAssociationComment(associationComment);
            landAssociation.setAssociationState(lk.icta.core.entity.LandAssociation.AssociationState.REJECTED);
        }

        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);

        Land l =landManagementService.getLandById(id);

        String landDetails=null;
        String emailSubject=null;
        if(isDefineAM){
            setLandLogComment("Rejected  defined alienation method ",l,user);
            emailSubject="Rejection of Defined Alienation Method for Land";
            String alienationMethod=landAssociation.getAlienationMethod().getEnName();
            landDetails="The defined alienation method of " + alienationMethod + " for the land referenced by serial number : " + l.getId();
        }
        else{
            setLandLogComment("Rejected  land association ",l,user);
            emailSubject="Rejection of Associated Alienation Methods for Land";
            landDetails="Associated alienation methods for the land referenced by serial number : " + l.getId();
        }
        //Update the land approval note
        landManagementService.updateLandApprovalNote(l,user);


        String emailedMessage= null;
        emailedMessage= landDetails + " has been rejected by ";

        GNDivision GNDiv = landManagementService.getLandById(id).getGnDivision();
        mailSenderService.notifyToAll(GNDiv, user,emailSubject, emailedMessage);

    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void rejectLandAlienationMetadataForDefineAM(long metadataId, String defineComment, User user) {
        lk.icta.core.entity.LandAssociation landAssociation = getAlienationMethodMetadataById(metadataId, user);
        landAssociation.setDefineComment(defineComment);
        landAssociation.setDefineAMState(lk.icta.core.entity.LandAssociation.DefineAMState.REJECTED);
        this.landAssociationDAO.updateLandAlienationMetadata(landAssociation, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllNotApprovedAlienationMethodMetadataByLand(long id, User user) {
        return landAssociationDAO.getAllNotApprovedAlienationMethodMetadataByLand(id, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAlienationMethodMetadataListByLand(long id, User user) {
        return landAssociationDAO.getAlienationMethodMetadataListByLand(id, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public lk.icta.core.entity.LandAssociation getAlienationMethodMetadataByLand(long id, User user) {
        return landAssociationDAO.getAlienationMethodMetadataByLand(id, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllAlienationMethodMetadataByAlienationMethod(int id, User user) {
        return landAssociationDAO.getAllAlienationMethodMetadataByAlienationMethod(id, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllApprovedDefineAlienationMethodMetadataByUser(User user) {
        return landAssociationDAO.getAllApprovedDefineAlienationMethodMetadataByUser(user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllApprovedDefineAlienationMethodMetadataByUserAndMethodId(int id, User user) {
        return landAssociationDAO.getAllApprovedDefineAlienationMethodMetadataByUserAndMethodId(id, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllApprovedDefineAlienationMethodMetadataByDs(int dsId, User user) {
        return landAssociationDAO.getAllApprovedDefineAlienationMethodMetadataByDs(dsId, user);
    }

    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<lk.icta.core.entity.LandAssociation> getAllApprovedDefineAlienationMethodMetadataByDistrict(int districtId, User user) {
        return landAssociationDAO.getAllApprovedDefineAlienationMethodMetadataByDistrict(districtId, user);
    }

    private void setLandLogComment(String approveStr, Land land, User user) {
        if (land.getApprovalNote() != null) {
            land.setApprovalNote(land.getApprovalNote() + "\n" + approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + ".");
        } else {
            land.setApprovalNote(approveStr + " by :" + user.getUsername() + ", Date : " + new Date() + ".");
        }
    }

    private void handleELandRuntimeException(String message, int code) {
        logger.error(message);
        throw new ELandRuntimeException(message, code);
    }
}
